-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: Address memory leaks in API provider clients and Bedrock handler cache #4239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Address memory leaks in API provider clients and Bedrock handler cache #4239
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @kiwina, I left some suggestions for this one.
Might be worth actually having a proper dispose implementation for each provider.
Curious to hear your thoughts.
| } | ||
| // Clear the reference on the instance | ||
| ;(this as any).client = undefined | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reflection-based approach using (this as any).client and typeof checks might be problematic for several reasons:
- Loss of type safety: TypeScript cannot verify that subclasses have a
clientproperty - Runtime dependency: Relies on property names and method names that might change
- Brittle assumptions: Assumes all clients will have one of three disposal methods (
close,destroy, ordispose)
Consider a more type-safe approach:
// Option 1: Make dispose abstract and let each provider implement it
export abstract class BaseProvider implements ApiHandler {
abstract dispose(): void;
}
// Option 2: Define a client contract
export abstract class BaseProvider implements ApiHandler {
protected abstract getClient(): { dispose?: () => void } | undefined;
public dispose(): void {
const client = this.getClient();
client?.dispose?.();
}
}This would ensure each provider explicitly handles its own cleanup logic with proper type checking.
|
|
||
| // Explicitly call dispose on the api handler again, in case abortTask didn't catch it | ||
| // or if dispose is called directly without abortTask. | ||
| this.api?.dispose?.() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that these are being called more than once.
abortTask(true)is called, which internally callsthis.api?.dispose?.()- Then
this.api?.dispose?.()is called again immediately after
This could lead to issues if the disposal method isn't idempotent.
| * Disposes of any resources held by the provider. | ||
| * Attempts common disposal methods on the client if it exists. | ||
| */ | ||
| public dispose(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several providers in the codebase have client properties but don't override dispose():
AnthropicHandlerhasprivate client: AnthropicOpenAiHandlerhasprivate client: OpenAIBaseOpenAiCompatibleProviderhasprivate client: OpenAI- Other OpenAI-compatible providers (Groq, Chutes, etc.)
These will rely on this reflection-based disposal, which:
- Won't work for
privateproperties (they're not accessible via reflection) - Assumes the SDK has one of the three 3 methods
As I said before, I think we should probably have each provider implement its own dispose() method to properly clean up its specific resources, otherwise this implementation doesn't actually do much for providers that don't have any of the 3 disposal methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets go with a dispose method on the provider, like i mentioned on another pr this would streamline the codebase to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to get it done in the next couple of days
|
|
||
| // Added new dispose method as per leak report suggestion | ||
| public dispose(): void { | ||
| console.log(`[subtasks] disposing task ${this.taskId}.${this.instanceId}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| console.log(`[subtasks] disposing task ${this.taskId}.${this.instanceId}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think generally we have way to little logging to trace problems. I did mention this before on discord.
we should have generally some better logging to aid in development, if we don't know whats going on it's has to fix something.
| } | ||
|
|
||
| // Added new dispose method as per leak report suggestion | ||
| public dispose(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dispose method will directly conflict with your PR #4233.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there are several PR that touch into the same concept of the Task class needing to dispose of everything it launches.
I could potentially put them all togheter, i belive keeping them seperated for now will give a better insight. merging them is trivial.
Technically speking, i agree, practically speaking they all need to be refactored. My fix takes into account a minimally invasive solution |
|
stale |
PR for API Provider Client Disposal (api_provider_clients) and Bedrock Handler Cache (bedrock_699)
This PR addresses two potential memory leaks:
leak-report/likely/api_provider_clients.md.previousCachePointPlacementsinAwsBedrockHandler, as identified inleak-report/likely/bedrock_699.md.Closes: #4238
Closes: #4240
Description
1. API Provider Clients:
API provider handlers (e.g.,
AnthropicHandler,OpenAIHandler) create SDK client instances but lacked explicitdispose()methods. TheTaskclass, which holds these handlers, also did not explicitly call for their disposal. This could lead to resource leaks. This patch introduces adispose()method to theBaseProviderclass (which is called reflectively byTask) and ensuresTask.abortTask()andTask.dispose()callthis.api?.dispose().2. Bedrock Handler Cache:
The
AwsBedrockHandlerclass uses an instance propertypreviousCachePointPlacementsto store cache point information. Entries were added but never removed. This patch overrides thedisposemethod inAwsBedrockHandlerto clear this map and callssuper.dispose().Related Bug Reports
Changes
src/api/providers/base-provider.ts:dispose()method that reflectively attempts to call common cleanup methods on athis.clientproperty.src/api/index.ts:dispose?: () => voidto theApiHandlerinterface.src/core/task/Task.ts:this.api?.dispose?.()toabortTask()and the newdispose()methods.src/api/providers/bedrock.ts:dispose()method inAwsBedrockHandlerto clearthis.previousCachePointPlacementsand callsuper.dispose().src/api/providers/vscode-lm.ts:overridekeyword tocountTokensmethod.How to Test
For API Provider Clients:
BaseProviderif client cleanup methods are found.For Bedrock Handler Cache:
conversationId.Task.dispose().AwsBedrockHandleror add logging to observe the clearing ofpreviousCachePointPlacements.